import typing
from selenium.webdriver.edge.webdriver import WebDriver
from selenium.webdriver.edge.options import Options
from selenium.webdriver.edge.service import Service
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.by import By

def find_element_smart(
    driver: WebDriver,
    source_element: typing.Union[WebElement, str, None],
    target_xpath: str,
    max_parent_levels: int = 5
) -> typing.Optional[WebElement]:
    """
    智能元素查找函数 - 根据锚点元素在其祖先元素中查找目标元素
    
    本函数实现了两种查找模式：
    1. 单锚点模式：当source_element是WebElement时，从该元素开始向上查找
    2. 多锚点竞速模式：当source_element是XPath字符串时，找到所有匹配元素，然后进行竞速查找
    
    查找逻辑：
    - 从锚点元素开始，逐级向上查找父元素/祖先元素
    - 在每一级的祖先元素的所有后代中查找目标元素
    - 实现"先找到先返回"的竞速机制
    
    Args:
        driver: WebDriver实例，用于执行查找操作
        source_element: 锚点元素，支持三种类型：
            - WebElement对象：直接使用该元素作为锚点
            - 字符串：作为XPath表达式查找所有匹配的锚点元素（竞速查找模式）
            - None：在全局范围内查找目标元素
        target_xpath: 目标元素的XPath表达式，注意：
            - 推荐使用相对路径格式，如"button"或"input[@type='text']"
            - 避免使用以"/"或"//"开头的绝对路径，这会导致查找范围超出预期
            - 函数会自动清理"./"或".//"前缀，确保路径拼接正确
        max_parent_levels: 最大向上查找的祖先元素层数（默认5层）
            - 层级1：直接父元素
            - 层级2：祖父元素
            - 层级3：曾祖父元素，以此类推
    
    Returns:
        WebElement: 找到的目标元素，如果未找到则返回None
        
    Raises:
        可能抛出Selenium相关的各种异常
        
    Example:
        # 单锚点模式：从已知元素向上查找
        username_input = find_element_smart(
            driver=driver,
            source_element=label_element,
            target_xpath="input[@type='text']",
            max_parent_levels=3
        )
        
        # 多锚点竞速模式：从所有匹配XPath的元素中竞速查找
        submit_button = find_element_smart(
            driver=driver,
            source_element="//span[contains(@class, 'label')]",
            target_xpath="button[text()='提交']",
            max_parent_levels=5
        )
        
        # 全局查找模式
        any_button = find_element_smart(
            driver=driver,
            source_element=None,
            target_xpath="button"
        )
    """
    
    def clean_xpath(xpath: str) -> str:
        """
        清理XPath表达式，移除不必要的相对路径前缀
        
        此函数确保XPath路径在拼接时不会产生重复的相对路径指示符
        例如：将".//button"转换为"button"，避免拼接后变成".//.//button"
        
        Args:
            xpath: 原始XPath表达式
            
        Returns:
            str: 清理后的XPath表达式
        """
        # 移除开头的相对路径指示符，避免在拼接时产生重复
        if xpath.startswith('./'):
            return xpath[2:]
        elif xpath.startswith('.//'):
            return xpath[3:]
        return xpath
    
    def get_ancestor_element(element: WebElement, level: int) -> typing.Optional[WebElement]:
        """
        获取指定层级的祖先元素
        
        使用ancestor轴获取元素的第n级祖先元素：
        - level=1: 直接父元素
        - level=2: 祖父元素
        - level=3: 曾祖父元素
        
        Args:
            element: 起始元素
            level: 祖先层级（1=父元素，2=祖父元素，以此类推）
            
        Returns:
            WebElement: 找到的祖先元素，如果不存在则返回None
        """
        if level < 1:
            return None
        
        try:
            # 使用ancestor轴获取指定层级的祖先元素
            # ancestor::*[1] 表示第一个祖先（直接父元素）
            # ancestor::*[2] 表示第二个祖先（祖父元素）
            xpath = f"./ancestor::*[{level}]"
            ancestor_elements = element.find_elements(By.XPATH, xpath)
            return ancestor_elements[0] if ancestor_elements else None
        except Exception as e:
            print(f"获取第{level}级祖先元素时出错: {e}")
            return None
    
    # 清理目标XPath，移除可能的前缀
    # 这一步很重要，确保后续的XPath拼接不会产生语法错误
    cleaned_target_xpath = clean_xpath(target_xpath)
    
    # ===== 处理全局查找模式（source_element为None） =====
    if source_element is None:
        try:
            # 构建完整的全局XPath
            # 如果target_xpath不是以//开头，需要添加//前缀进行全局查找
            if cleaned_target_xpath.startswith('//'):
                full_xpath = cleaned_target_xpath
            else:
                full_xpath = f"//{cleaned_target_xpath}"
            
            # 在全局范围内查找目标元素
            elements = driver.find_elements(By.XPATH, full_xpath)
            if elements:
                print(f"全局查找成功：找到 {len(elements)} 个匹配元素")
                return elements[0]  # 返回第一个匹配的元素
            else:
                print(f"全局查找失败：未找到匹配 '{cleaned_target_xpath}' 的元素")
                return None
                
        except Exception as e:
            print(f"全局查找过程中出错: {e}")
            return None
    
    # ===== 处理单锚点模式（source_element是WebElement） =====
    if isinstance(source_element, WebElement):
        print("单锚点模式：从单个锚点元素开始查找")
        
        # 逐级向上查找祖先元素，从最近的父元素开始（层级1）
        for current_level in range(1, max_parent_levels + 1):
            try:
                # 获取当前层级的祖先元素
                ancestor_element = get_ancestor_element(source_element, current_level)
                if ancestor_element is None:
                    # 没有更多祖先元素，停止查找
                    print(f"已达到最大祖先层级: {current_level-1}")
                    break
                
                # 在祖先元素的所有后代中查找目标元素
                # 使用".//"表示在祖先元素的所有后代中查找
                target_elements = ancestor_element.find_elements(By.XPATH, f".//{cleaned_target_xpath}")
                
                if target_elements:
                    print(f"单锚点查找成功：在第{current_level}级祖先元素中找到目标")
                    return target_elements[0]  # 返回找到的第一个目标元素
                    
            except Exception as e:
                print(f"在第{current_level}级祖先查找时出错: {e}")
                # 继续尝试下一层级
                continue
        
        print(f"单锚点查找失败：在{max_parent_levels}层祖先范围内未找到目标")
        return None
    
    # ===== 处理多锚点竞速模式（source_element是XPath字符串） =====
    elif isinstance(source_element, str):
        try:
            print(f"多锚点竞速模式：查找锚点元素 '{source_element}'")
            
            # 获取所有匹配的锚点元素
            # 这里使用find_elements而不是find_element，因为可能有多个匹配项
            anchor_elements = driver.find_elements(By.XPATH, source_element)
            
            if not anchor_elements:
                print(f"竞速查找失败：未找到XPath为 '{source_element}' 的锚点元素")
                return None
            
            print(f"竞速查找开始：找到 {len(anchor_elements)} 个锚点元素")
            
            # 按层级递增进行竞速查找
            # 从最近的层级开始查找（效率最高）
            for current_level in range(1, max_parent_levels + 1):
                candidates = []  # 存储找到的候选元素及其信息
                
                # 在当前层级，对所有锚点元素进行同步查找
                # 这是竞速查找的核心：多个锚点在同一层级竞争
                for anchor_idx, anchor_elem in enumerate(anchor_elements):
                    try:
                        # 获取当前锚点元素的指定层级祖先
                        ancestor_element = get_ancestor_element(anchor_elem, current_level)
                        if ancestor_element is None:
                            # 该锚点元素没有当前层级的祖先，跳过
                            continue
                        
                        # 在祖先元素的所有后代中查找目标元素
                        target_elements = ancestor_element.find_elements(By.XPATH, f".//{cleaned_target_xpath}")
                        
                        if target_elements:
                            # 记录找到的元素、层级和锚点索引
                            # 这里使用find_elements[0]而不是find_element，避免NoSuchElementException异常
                            # print(f"多锚点竞速查找成功：在第{current_level}级祖先元素中找到目标")
                            # print(f"锚点索引: {anchor_idx}")
                            # print(f"层级: {current_level}")
                            # print(f"共同祖先标签: {ancestor_element.get_attribute('outerHTML')}")
                            # print(f"原始标签: {anchor_elem.tag_name}")
                            # print(f"原始标签: {anchor_elem.get_attribute('outerHTML')}")
                            # return target_elements[0]  # 立即返回第一个找到的元素
                            candidates.append({
                                'element': target_elements[0],
                                'level': current_level,
                                'anchor_index': anchor_idx,
                                'ancestor_tag': ancestor_element.tag_name
                            })
                            
                    except Exception as e:
                        # 单个锚点查找失败不影响其他锚点
                        # 这是竞速查找的重要特性：一个锚点失败不影响其他锚点的查找
                        print(f"锚点{anchor_idx}在第{current_level}级查找时出错: {e}")
                        continue
                    
                # 如果当前层级有找到目标元素，选择最优的返回
                if candidates:
                    # 选择策略：优先层级小的（距离近），然后选择锚点索引小的（文档顺序靠前）
                    best_candidate = min(candidates, key=lambda x: (x['level'], x['anchor_index']))
                    
                    print(f"竞速查找成功：在层级{best_candidate['level']}找到目标 "
                          f"（锚点索引{best_candidate['anchor_index']}, "
                          f"祖先元素: {best_candidate['ancestor_tag']})")
                    
                    return best_candidate['element']
            
            # 所有层级都查找完毕，未找到目标
            print(f"竞速查找失败：在所有{len(anchor_elements)}个锚点元素的"
                  f"{max_parent_levels}层祖先范围内均未找到目标")
            return None
            
        except Exception as e:
            print(f"竞速查找过程中出错: {e}")
            return None
    
    else:
        # 不支持的source_element类型
        print(f"错误：不支持的source_element类型: {type(source_element)}")
        return None

import requests

def Goto_CurrentActivateUrl(driver):
    """跳转到当前激活的页面"""
    debug_json_data = requests.get(
        url="http://127.0.0.1:9652/json", timeout=5).json()

    # 判断获取回来的数据是否正确
    if not isinstance(debug_json_data, list):
        return None

    # 过来掉不需要的 page 页面
    filter_json_data = list(
                                    filter(lambda item: (
                                        item['type'] == "page" and
                                        not item['title'].lower().startswith("devtools") and
                                        not item['title'].lower().startswith("offscreen") and
                                        not item['title'].lower().startswith("新标签页") and
                                        not item['url'].lower().startswith("edge://newtab/") and
                                        not item['url'].lower().startswith("chrome-extension") and
                                        not item['url'].lower().startswith("chrome-devtools")
                                    ), debug_json_data)
                                )
    current_activate_handle = filter_json_data[0]['id']
    driver.switch_to.window(current_activate_handle)
    


def by_text_find_element(driver: WebDriver,text: str,text_type:typing.Literal["equal","contains"],target_xpath: str,max_parent_levels: int = 5) -> typing.Optional[WebElement]:
    if text_type=="equal":
        source_xpath = f"//*[self::span[text()='{text}'] or self::label[text()='{text}']]"
    elif text_type=="contains":
        source_xpath = f"//*[self::span[contains(text(),'{text}')] or self::label[contains(text(),'{text}')]]"
    
    return find_element_smart(driver=driver,source_element=source_xpath,target_xpath=target_xpath,max_parent_levels=max_parent_levels)

def by_text_find_input_element(driver: WebDriver,text: str,text_type:typing.Literal["equal","contains"],target_xpath: str = "input",max_parent_levels: int = 5) -> typing.Optional[WebElement]:
    if text_type=="equal":
        source_xpath = f"//*[self::span[text()='{text}'] or self::label[text()='{text}']]"
    elif text_type=="contains":
        source_xpath = f"//*[self::span[contains(text(),'{text}')] or self::label[contains(text(),'{text}')]]"
    
    return find_element_smart(driver=driver,source_element=source_xpath,target_xpath=target_xpath,max_parent_levels=max_parent_levels)

def by_text_find_inputUpload_Element(driver: WebDriver,text: str,text_type:typing.Literal["equal","contains"],target_xpath: str = "input[@type='file']",max_parent_levels: int = 5) -> typing.Optional[WebElement]:
    if text_type=="equal":
        source_xpath = f"//*[self::span[text()='{text}'] or self::label[text()='{text}']]"
    elif text_type=="contains":
        source_xpath = f"//*[self::span[contains(text(),'{text}')] or self::label[contains(text(),'{text}')]]"
    
    return find_element_smart(driver=driver,source_element=source_xpath,target_xpath=target_xpath,max_parent_levels=max_parent_levels)

def by_text_find_inputUploadImage_Element(driver: WebDriver,text: str,text_type:typing.Literal["equal","contains"],target_xpath: str = "input[@type='file'][contains(@accept,'image')]",max_parent_levels: int = 5) -> typing.Optional[WebElement]:
    if text_type=="equal":
        source_xpath = f"//*[self::span[text()='{text}'] or self::label[text()='{text}']]"
    elif text_type=="contains":
        source_xpath = f"//*[self::span[contains(text(),'{text}')] or self::label[contains(text(),'{text}')]]"
    
    return find_element_smart(driver=driver,source_element=source_xpath,target_xpath=target_xpath,max_parent_levels=max_parent_levels)

def by_text_find_inputUploadVideo_Element(driver: WebDriver,text: str,text_type:typing.Literal["equal","contains"],target_xpath: str = "input[@type='file'][contains(@accept,'video')]",max_parent_levels: int = 5) -> typing.Optional[WebElement]:
    if text_type=="equal":
        source_xpath = f"//*[self::span[text()='{text}'] or self::label[text()='{text}']]"
    elif text_type=="contains":
        source_xpath = f"//*[self::span[contains(text(),'{text}')] or self::label[contains(text(),'{text}')]]"
    
    return find_element_smart(driver=driver,source_element=source_xpath,target_xpath=target_xpath,max_parent_levels=max_parent_levels)





# 根据文本找元素
# 根据文本找输入框
# 根据文本找按钮
# 根据文本找上传
# 根据文本找上传input单文件
# 根据文本找上传input多文件

# # ===== 使用示例和说明 =====
# if __name__ == "__main__":
#     """
#     使用示例和配置说明
    
#     配置Edge浏览器驱动并连接到已打开的浏览器实例
#     """
#     # 创建浏览器选项，连接到已打开的浏览器实例
#     options = Options()
#     options.add_experimental_option("debuggerAddress", "127.0.0.1:9652")
    
#     # 创建服务实例（使用默认配置）
#     service = Service()
    
#     # 创建WebDriver实例，连接到已打开的浏览器
#     driver = WebDriver(options=options, service=service)
    
#     # 导航到目标页面（可选，如果浏览器已经打开正确页面）
#     # driver.get("https://fxg.jinritemai.com/login/common")
#     Goto_CurrentActivateUrl(driver=driver)
#     print(driver.current_url)
#     import time
#     time.sleep(2)
    
#     element = by_text_find_input_element(driver=driver,text="库存",text_type="equal",target_xpath="input",max_parent_levels=10)
#     element.send_keys("8888")
    
    
#     # element = by_text_find_element(
#     #     driver=driver,
#     #     text="无法登录?",
#     #     text_type="equal",
#     #     target_xpath="button[text()='登录']",
#     #     max_parent_levels=20
#     # )
#     # print(element)
#     # # 使用示例1：多锚点竞速查找模式
#     # # 查找所有span元素作为锚点，然后在它们的祖先中查找登录按钮
#     # element = find_element_smart(
#     #     driver=driver,
#     #     source_element="//span",  # 锚点XPath：查找所有span元素
#     #     target_xpath="button[text()='登录']",  # 目标XPath：查找登录按钮
#     #     max_parent_levels=20  # 最大查找层级
#     # )
    
#     # # 输出找到的元素信息
#     # if element:
#     #     print("找到目标元素:")
#     #     print(f"标签名: {element.tag_name}")
#     #     print(f"类名: {element.get_attribute('class')}")
#     #     # 可以继续对找到的元素进行操作，如点击、输入等
#     #     # element.click()  # 点击元素
#     # else:
#     #     print("未找到目标元素")